package tv.geir.slackspace.code;

import java.io.File;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * An instance of this class is used to hold information about what packages
 * should be used and which should not. Packages can be added to inclusion 
 * list or to the ignore/exclusion list. Use includeClass() to determine
 * wheter a class should be included or not according to the current configuration. 
 *
 * @author Geir Gullestad Pettersen
 *
 */
public class PackageConfiguration {

	private static PackageConfiguration instance = null;
	private Set<String> packagesToExclude;
	private Set<String> packagesToInclude;
	
	public PackageConfiguration() {
		reset();
	}
	
	/**
	 * Resets the configuration back to default.
	 */
	public void reset() {
		//initIgnorePackages();
		packagesToInclude = new LinkedHashSet<String>();
		packagesToExclude = new LinkedHashSet<String>();
	}
	
	/**
	 * 
	 * @param className
	 * @return
	 */
	public boolean includeClass( String className ) {
		validateClassName( className );
		
		if( isInIgnoreList( className )) {
			/* class is in ignore liste*/
			return false;
		} else if( packagesToInclude.size() > 0) {
			/* there are user configured inclusions, only allow this
			 * class to be include if it is a part of that inclusion list */
			return isInIncludeList(className);
		} else {
			/* not in ignore list and include-list is empty (=no restrictions*/
			return true;
		}
		
	}

	public void addIgnorePackage( String packageName ) {
		validatePackageName(packageName);
		packagesToExclude.add( packageName );
	}
	
	public void addAllowedPackage( String packageName ) {
		validatePackageName(packageName);
		packagesToInclude.add( packageName );
	}
	
	public static PackageConfiguration getInstance() {
		if( instance == null ) {
			instance = new PackageConfiguration();
		}
		return instance;
	}

	
	
	
	/**
	 * Validates <code>packageName</code>, IllegalArgumentException is thrown
	 * whenever an invalid package name is used. Valid packagenames are defined by
	 * the Java(tm) specification, and in addition to that it has to end with a dot (.) 
	 * @param packageName 
	 */
	private void validatePackageName( String packageName) {
		char[] chars = getClassNameCharacters(packageName);
		// no invalid characters, make sure the package name ends with a dot.
		char lastChar = chars[ chars.length -1 ]; 
		if( lastChar != '.' ) {
			throw new IllegalArgumentException( "Invalid last character \'" +
					lastChar + ", packagenames must end with a \'.\'");
		}
	}

	/**
	 * @param packageName
	 * @return
	 */
	private char[] getClassNameCharacters(String packageName) {
		char[] chars = packageName.toCharArray();
		for( char c : chars ) {
			if( !isValidPackageChar(c)) {
				throw new IllegalArgumentException( "Invalid character in package name \'" +
						c + "\'");
			}
		}
		return chars;
	}
	
	private void validateClassName( String className ) {
		getClassNameCharacters( className );
	}
	
	private boolean isValidPackageChar( char c ) {
		// TODO Implement isValidPackageChar( char c )!
		return 
			c != File.separatorChar && c != '/';
		
	}
	
	
	/**
	 * Returns true if <code>className</code> is on ignore list.
	 * @param className
	 * @return
	 */
	private boolean isInIgnoreList( String className ) {
		if( className.indexOf( '/' ) > 0 ) {
			className.replace('/', '.');
		}
		for( String p : packagesToExclude ) {
			if( classInPackage(	className, p)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns true if <code>className</code> is on include list.
	 * @param className
	 * @return
	 */
	private boolean isInIncludeList( String className ) {
		for( String p: packagesToInclude ) {
			if( classInPackage(className, p)) {
				return true;
			}
		}
		return false;
	}

	public Set<String> getPackagesToExclude() {
		return packagesToExclude;
	}

	public void setPackagesToExclude(Set<String> packagesToExclude) {
		this.packagesToExclude = packagesToExclude;
	}

	public Set<String> getPackagesToInclude() {
		return packagesToInclude;
	}

	public void setPackagesToInclude(Set<String> packagesToInclude) {
		this.packagesToInclude = packagesToInclude;
	}
	
	private boolean classInPackage( String className, String pack ) {
		if( className.indexOf('/') > 0 ) {
			className = className.replace('/', '.');
		}
		if( className.startsWith( pack )) {
			return true;
		}
		return false;
	}
	
}
